package binary.reader

import binary.*
import binary.module.CustomSec

/**
 *@author haypo
 *@since 2021/7/21
 */
class WasmReader(var data: ByteArray) {

    fun remaining(): Int {
        return data.size
    }

    fun readU32(): UInt {
        if (data.size < 4) {
            panic(ErrUnexpectedEnd)
        }
        val n = data.readUInt32LE()
        data = data.drop(4).toByteArray()
        return n
    }


    fun readModule(module: Module) {
        if (remaining() < 4) {
            panic("unexpected end of magic header")
        }

        module.Magic = readU32();
        if (module.Magic != MagicNumber) {
            panic("magic header not detected")
        }

        if (remaining() < 4) {
            panic("unexpected end of binary version")
        }

        module.Version = readU32();
        if (module.Version != Version) {
            panic("unknown binary version: ${module.Version}")
        }

        ReadSections(module)
        if (module.FuncSec.size != module.CodeSec.size) {
            panic("function and code section have inconsistent lengths")
        }

        if (remaining() > 0) {
            panic("junk after last section")
        }
    }

    fun ReadSections(module: Module) {
        var prevSecID: Byte = 0
        //需要初始化空数组
        module.CustomSecs = emptyArray()
        while (remaining() > 0) {
            var secID = ReadByte();
            if (secID == SecCustomID) {
                val tail = readCustomSec();
                module.CustomSecs = module.CustomSecs.plus(tail)
                continue;
            }

            if (secID > SecDataID) {
                panic("malformed section id: ${secID}");
            }

            if (secID <= prevSecID) {
                panic("junk after last section, id: ${secID}");
            }

            prevSecID = secID;

            val n = ReadVarU32();
            var remainingBeforeRead = remaining();
            readNonCustomSec(secID, module);
            if (remaining().toUInt().plus(n) != remainingBeforeRead.toUInt()) {
                panic("section size mismatch, id: $secID")
            }
        }
    }    // bytes & name
    fun readBytes(): ByteArray {
        //先读取长度
        val n: UInt = ReadVarU32()
        if (data.size < n.toInt()) {
            panic(ErrUnexpectedEnd)
        }
        val bytes: ByteArray = data.take(n.toInt()).toByteArray()
        data = data.drop(n.toInt()).toByteArray()
        return bytes
    }
    fun readName(): String {
        val data = readBytes()
        val name =  data.asSequence().toString()
        if (!name.equals(data)) {
           panic("malformed UTF-8 encoding")
        }
        return name
    }
    private fun readCustomSec(): CustomSec {
        val secReader = WasmReader(data = readBytes())
        return CustomSec(
            secReader.readName(),
            secReader.data // TODO
        )
    }

    private fun readNonCustomSec(secID: Byte, module: Module) {
        when (secID) {
            SecTypeID -> module.TypeSec = readTypeSec()
            //todo SecImportID -> module.ImportSec = readImportSec()
            //todo SecFuncID -> module.FuncSec = readIndices()
            //todo    SecTableID -> module.TableSec = readTableSec()
            //todo    SecMemID -> module.MemSec = ReadMemSec()
            //todo    SecGlobalID -> module.GlobalSec = readGlobalSec()
            //todo     SecExportID -> module.ExportSec = ReadExportSec()
            //todo    SecStartID -> module.StartSec = ReadStartSec()
            //todo      SecElemID -> module.ElemSec = ReadElemSec()
            //todo    SecCodeID -> module.CodeSec = ReadCodeSec()
            //todo      SecDataID -> module.DataSec = ReadDataSec()
        }
    }
    // fixed length value
    fun ReadByte(): Byte {
        if (data.isEmpty()) {
            panic(ErrUnexpectedEnd)
        }

        val bytes = data[0];
        data = data.drop(1).toByteArray()
        return bytes
    }

    // variable length value
     fun ReadVarU32(): UInt {
        val (n, w) = decodeVarUint(data, 32)
        data = data.drop(w).toByteArray()
        return n.toUInt()
    }
}


fun decode(data: ByteArray): Pair<Module, Exception?> {
    val module = Module()
    try {
        val reader = WasmReader(data)
        reader.readModule(module)
        return Pair(module, null);
    } catch (e: Exception) {
        return Pair(module, e)
    }
}